import yaml, os
from types import DictType, ListType
from copy import deepcopy
from glog import Logged

class Covert_Helper():
	STR = 'str'
	INT = 'int'
	INT8 = 'int8'
	INT16 = 'int16'
	BOOL = 'bool'
	ARRAY = 'array'
	FLOAT = 'float'
	DICT = 'dict'
	LIST = 'list'
	def convert_value(self, v, obj_type):
		try:
			if v is None:
				return None		
			if obj_type == self.STR:
				v = str(v)
			elif obj_type == self.INT:
				v = int(v)
			elif obj_type == self.INT8:
				v = int(v)
				if v > 255 or v < 0:
					self.log_error('wrong range for %s: %d' % (obj_type, v))
					return None
			elif obj_type == self.INT16:
				v = int(v)
				if v > 65535 or v < 0:
					self.log_error('wrong range for %s: %d' % (obj_type, v))
					return None
			elif obj_type == self.BOOL:
				v = bool(v)
			elif obj_type == self.ARRAY:
				v = list(v)
			elif obj_type == self.FLOAT:
				v = float(v)
			elif obj_type == self.DICT and type(v) == DictType:
				pass
			elif (obj_type == self.ARRAY or obj_type == self.LIST) and type(v) == ListType:
				pass
			elif type(obj_type) == ListType:
				if v not in obj_type:
					self.log_error("proper values for this field are: %s not '%s'" % (str(obj_type), v))
				pass
			else:
				self.log_error('wrong type: %s' % obj_type)
				return None
		except ValueError, e:
			self.log_error(str(e))
			v = None

		return v

class Config(Logged, Covert_Helper):
	o = {} # objects
	s = {} # schemas
	oid_cnt = 1
	name = 'config'

	_instance = None
	def __new__(cls, *args, **kwargs):
		if not cls._instance:
			cls._instance = super(GLog, cls).__new__(cls, *args, **kwargs)
		return cls._instance
	
	def read_yaml_file(self, file_name, skipTemplating = False):
		if not os.path.exists(file_name):
			self.log_error('No such file %s' % file_name)
			return None

		try:
			stream = file(file_name, 'r')
			file_cont = stream.read()
			stream.close()
		except Exception, e:
			self.log_error(str(e))
			return None

		cont = None
		if skipTemplating:
			self.params = {}

		from jinja2 import Template, Environment, DebugUndefined, FileSystemLoader
		# some parameters are Jinja2 templates so should not replace {{ var }} to empty string
		# DebugUndefined leaves it as is
		try:
			cont = Environment(undefined = DebugUndefined, loader = FileSystemLoader('.')).from_string(file_cont).render(self.params)
		except TypeError, e:
			self.log_error(e)
			return None

		ret = None
		try:
			ret = yaml.load(cont)
		except Exception, e:
			self.log_error(str(e))
			return None

		return ret

	def readConfig(self, file_name, skipTemplating = False):
		self.instance = os.path.splitext(os.path.basename(file_name))[0]
		self.params = {}
		self.params['instance'] = self.instance

		self.schema = self.read_yaml_file(os.path.dirname(__file__) + '/' + 'config.yaml', skipTemplating)
		if self.schema is None:
			return False

		self.params['gmultiserver_version'] = self.get_pkg('version')
		try: from netifaces import interfaces, ifaddresses, AF_INET
		except ImportError:
			pass
		else:
			import socket
			for ifaceName in interfaces():
				ip = [i['addr'] for i in ifaddresses(ifaceName).setdefault(AF_INET, [{'addr': None}] )][0]
				if ip is not None:
					self.params[ifaceName + '_ip'] = ip
					#self.params[ifaceName + '_name'] = socket.gethostbyaddr(ip)[0]
	
		self.config = self.read_yaml_file(file_name)
		if self.config is None:
			return False

		return True

	def get(self, val):
		ret = None
		if val in self.config:
			ret = self.config[val]
		return ret

	def get_schema(self, val):
		if val in self.s:
			return self.s[val]
		ret = None
		if val in self.schema['classes']:
			ret = self.schema['classes'][val]
			if 'inherit' in ret:
				i = deepcopy(self.get_schema(ret['inherit']))
				if 'params' in i:
					if 'params' in ret:
						i['params'].update(ret['params'])
					ret['params'] = i['params']
				if 'props' in i:
					if 'props' in ret:
						i['props'].update(ret['props'])
					ret['props'] = i['props']
				if 'methods' in i:
					if 'methods' in ret:
						i['methods'].update(ret['methods'])
					ret['methods'] = i['methods']
		self.s[val] = ret
		return ret

	def get_pkg(self, val):
		if val in self.schema['package']:
			return self.schema['package'][val]
		return None
	
	def get_obj(self, name, copy = False):
		if not copy and name in self.o:
			return self.o[name]
		
		q = self.get(name)
		if q is None:
			if name == 'main':
				q = {}
				q['type'] = name			
			else:
				self.log_error('No such object: %s' % name)
				return None

		o = None
		obj_type = None
		if 'type' in q:
			obj_type = q['type']
		else:
			if name == 'main':
				obj_type = name
			else:
				self.log_error('No type')
				return None
		c = self.get_schema(obj_type)

		if c is None:
			print "No such class: %s" % obj_type
			return None

		module = c['module']
		try:
			O = __import__(module, fromlist = c['class_name'])
			C = O.__getattribute__(c['class_name'])
		except Exception, e:
			print 'class %s' % c['class_name']
			print 'ERROR get_obj: %s' % e
			return None

		p = {}
		if 'hidden_def' in c:
			if 'name' in  c['hidden_def']:
				p['name'] = name
			if 'pool' in  c['hidden_def']:
				p['pool'] = self.pool

		if 'params' in c:
			for i in c['params']:
				if i in q:
					p[i] = self.convert_value(q[i], c['params'][i]['type'])
				elif 'def' in c['params'][i]:
					p[i] = c['params'][i]['def']
				else:
					p[i] = None
			o = C(**p)
		else:
			o = C()
		o.obj_type = obj_type
		o.name = name

		if 'props' in c:
			for i in c['props']:
				if i in q:
					v = self.convert_value(q[i], c['props'][i]['type'])
					if v is None:
						continue
					try:
						setattr(o, i, q[i])
					except AttributeError:
						print 'wrong arg: %s' % i
				else:
					if 'opt' in c['props'][i] and c['props'][i]['opt'] == True:
						continue
					if 'def' in c['props'][i]:
						setattr(o, i, c['props'][i]['def'])
					else:
						setattr(o, i, None)
			setattr(o, '_changed', False)

		setattr(o, 'oid', self.oid_cnt)
		setattr(o, 'obj_type', obj_type)
		if name == 'main':
			self.main = o
		if not copy:
			self.oid_cnt += 1
			self.o[name] = o
		return o

	def get_msg(self, name):
		if name not in self.o:
			q = self.get(name)
			if q is None:
				return None
			m = None
			if q['type'] == 'multi_message':
				from email.mime.multipart import MIMEMultipart
				m = MIMEMultipart()
				for c in q['contain']:
					m.attach(self.get_msg(c))
			else:
				from email.message import Message
				m = Message()
				m.add_header('MIME-Version', '1.0')
				if 'text' in q:
					m.set_payload(q['text'])
				elif 'base64' in q:
					m.set_payload(q['base64'])
					m.add_header('Content-Transfer-Encoding', 'base64')
				elif 'file' in q:
					try:
						m.set_payload(open(q['file'], 'r').read())
					except Exception, e:
						print e
						import sys
						sys.stderr.write(str(e) + '\n')
					else:
						m.add_header('Content-Transfer-Encoding', '8bit')
				elif 'rfc822file' in q:
					from mime import message_from_string
					from utils import read_data
					rfc822_data = read_data(q['rfc822file'])
					if rfc822_data is not None:
						m = message_from_string(rfc822_data)
				elif 'rfc822' in q:
					from mime import message_from_string
					m = message_from_string(q['rfc822'])
				elif 'binary' in q:
					m.set_payload(self.get(q['binary']))
				else:
					self.log_error('message without content')

			if 'headers' in q:
				for h in q['headers']:
					if h == 'Content-Transfer-Encoding' and m.get('Content-Transfer-Encoding'):
						m.replace_header(h, q['headers'][h])
					else:
						m.add_header(h, q['headers'][h])
			from imaplib import Time2Internaldate
			import time
			date = Time2Internaldate(time.time())
			if 'date' in q and q['date'] != 'now':
				date = q['date']
			m.date = date

			self.o[name] = m
			return m
		return self.o[name]

